package cn.edu.ctgu.geo;

import cn.edu.ctgu.geo.constants.GridData;
import cn.edu.ctgu.geo.type.*;

public class Codec3D {
    /**
     * 对大地坐标进行编码
     * @param lngLatEle 大地坐标，类型为 LngLat & number
     * 编码级别,默认10级,地球长半轴，默认取6378137m
     * @returns 32位的北斗三维网格位置码
     */
    public static String encode(LngLatEle lngLatEle) {
        return encode(lngLatEle, 6378137, 10);
    }
    /**
     * 对大地坐标进行编码
     * @param lngLatEle 大地坐标，类型为 LngLat & number
     * @param level 编码级别,默认10级
     * @param r 地球长半轴，默认取6378137m
     * @returns 32位的北斗三维网格位置码
     */
    public static String encode(LngLatEle lngLatEle, double r,int level) {
        if (r == 0) {
            r = 6378137;
        }
        // 计算二维网格位置码，20 位
        String code2D = Codec2D.encode(lngLatEle);
        // 计算高程方向编码，12 位
        String codeEle = encodeElevation(lngLatEle.getElevation(), r,level);
        return code2D + codeEle;
    }
    /**
     * 对大地坐标进行编码
     * @param lngLatEle 大地坐标，类型为 LngLat & number
     * @param level 编码级别,  二维编码根据级别走，高程编码固定12位，不做范围
     * @returns 32位(随着编码级别变，前20位二维变，后12位高程编码不变)的北斗三维网格位置码
     */
    public static String encode2(LngLatEle lngLatEle, int level) {
        // 计算二维网格位置码，编码长度随级别走
        String code2D = Codec2D.encode(lngLatEle,level);
        // 计算高程方向编码，固定12 位 固定10级
        String codeEle = encodeElevation(lngLatEle.getElevation(), 6378137,10);
        return code2D + codeEle;
    }

    /**
     * 对高程进行编码
     * @param elevation 高程，单位米
     * @param r 地球长半轴，默认取6378137m
     * @param level 编码层级，默认10
     * @returns 高程方向编码
     */
    public static String encodeElevation(double elevation, double r, int level) {
        if (r == 0) {
            r = 6378137;
        }
        if (level == 0) {
            level = 10;
        }
        // 计算θ=1/2048″时的大地方向网格计数
        double theta = 1.0 / 2048 / 3600;
        double theta0 = 1;
        int n = (int) Math.floor(
                (theta0 / theta) *
                        (Math.log((elevation + r) / r) / Math.log(1 + theta0 * (Math.PI / 180)))
        );
        // 单独处理符号位
        String signCode = n < 0 ? "1" : "0";
        // 补位 31 位
        String nb = String.format("%31s", Integer.toBinaryString(Math.abs(n))).replace(' ', '0');
        nb = signCode + nb;
        // 高程方向编码结果
        StringBuilder codeEle = new StringBuilder();
        // 二进制位数下标
        int index = 0;
        for (int i = 0; i <= level; i++) {
            // 截取字符串
            String str = nb.substring(index, index += GridData.elevationParams[i][0]);
            int num = Integer.parseInt(str, 2);
            String codeI = Integer.toString(num, GridData.elevationParams[i][1]).toUpperCase();
            if (i == 1) {
                // 如果是第一级，需要补位
                codeI = String.format("%2s", codeI).replace(' ', '0');
            }
            codeEle.append(codeI);
        }
        return codeEle.toString();
    }

    /**
     * 对32位北斗三维网格位置码解码
     * @param code 32位北斗三位网格位置码
     * decodeOption 解码选项
     * r 地球长半轴，默认取6378137m
     * @returns 大地坐标
     */
    public static LngLatEle decode(String code) {
        return decode(code, new DecodeOption("dms"), 6378137);
    }
    /**
     * 对32位北斗三维网格位置码解码
     * @param code 32位北斗三位网格位置码
     * @param decodeOption 解码选项
     * @param r 地球长半轴，默认取6378137m
     * @returns 大地坐标
     */
    public static LngLatEle decode(String code, DecodeOption decodeOption, double r) {
        if (r == 0) {
            r = 6378137;
        }
        if (decodeOption == null) {
            decodeOption = new DecodeOption("dms");
        }
        if (code.length() != 32) {
            throw new IllegalArgumentException("编码长度不符合");
        }
        // 截取字符串
        String code2D = code.substring(0, 20);
        String codeEle = code.substring(20, 32);
        // 分别解码
        LngLat lngLat = Codec2D.decode(code2D, decodeOption);
        double elevation = decodeElevation(codeEle, r);
        return new LngLatEle(lngLat, elevation);
    }

    /**
     * 对二维随级别变化，高程固定12位北斗三维网格位置码解码
     * @param code 二维随级别变化，高程固定12位北斗三位网格位置码
     * @param decodeOption 解码选项
     * @param r 地球长半轴，默认取6378137m
     * @returns 大地坐标
     */
    public static LngLatEle decode2(String code, DecodeOption decodeOption) {
        double r = 6378137;
        if (decodeOption == null) {
            decodeOption = new DecodeOption("dms");
        }
        // 截取高程后12位
        String code3D = code.substring(code.length() - 12);
        String code2D = code.substring(0,code.length() - 12);
        // 分别解码
        LngLat lngLat = Codec2D.decode(code2D, decodeOption);
        double elevation = decodeElevation(code3D, r);
        return new LngLatEle(lngLat, elevation);
    }

    /**
     * 对高程方向编码解码
     * @param codeEle 高程方向编码
     * @param r 地球长半轴，默认取6378137m
     * @returns 高程，单位米
     */
    public static double decodeElevation(String codeEle, double r) {
        if (r == 0) {
            r = 6378137;
        }
        // 补充长度
        if (codeEle.length() < 12) {
            codeEle = String.format("%-12s", codeEle).replace(' ', '0');
        }
        // 如果长度不等于 12 即错误
        if (codeEle.length() != 12) {
            throw new IllegalArgumentException("高程方向编码长度错误");
        }
        // 处理符号
        int sign = codeEle.charAt(0) == '0' ? 1 : -1;
        StringBuilder elevationStr = new StringBuilder();
        // 单独处理第一级
        String code1st = codeEle.substring(1, 3);
        elevationStr.append(String.format("%"+GridData.elevationParams[1][0]+"s", Integer.toBinaryString(Integer.parseInt(code1st, GridData.elevationParams[1][1]))).replace(' ', '0'));

        for (int n = 2; n <= 10; n++) {
            // 处理第 2 级到第 10 级
            String codeN = codeEle.substring(n + 1, n + 2);
            elevationStr.append(String.format("%"+GridData.elevationParams[n][0]+"s", Integer.toBinaryString(Integer.parseInt(codeN, GridData.elevationParams[n][1]))).replace(' ', '0'));
        }
        // 计算高程方向网格数量
        int n = sign * Integer.parseInt(elevationStr.toString(), 2);
        double theta = 1.0 / 2048 / 3600;
        double theta0 = 1;
        // 计算高度
        return Math.pow(1 + theta0 * (Math.PI / 180), n * (theta / theta0)) * r - r;
    }

    public static void main(String[] args) {
        LngLatEle lngLat = new LngLatEle(116, 18.0, 45.37, LngDirection.E,
                39, 59.0, 35.38, LatDirection.N,200);

        System.out.println(lngLat);

        final int level = 1;
        String code = Codec3D.encode(lngLat);
        String code2 = Codec3D.encode2(lngLat,6);
        System.out.println("北斗网格位置码：("+level+"):"+code2+":"+code2.length());
        System.out.println("北斗网格位置码：("+level+"):"+code+":"+code.length());
        LngLatEle result = Codec3D.decode(code);
        LngLatEle result1 = Codec3D.decode2(code2,null);
        System.out.println(result);
        System.out.println("\n-------------"+result1);

    }
}
